package pho.prowon.category;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Observer;
import java.util.SortedSet;
import java.util.TreeSet;

import pho.prowon.ImageDataContainer;

//@TODO (CPA) convenient image information window with preview of image and next/previous button to skip

/**
 * @author Peter
 * @version 1.0
 *
 * The abstract base class of all Category Classes. It implements primarily the 
 * 'can contain other categories'-behaviour, a name and a parent property. This is all internal
 * behavior. Most of the public behaviour is accessible via the RootIndexCategory.
 *
 */

public abstract class IndexCategory implements Comparable, Observer 
{
	// Properties
	private SortedSet categories = new TreeSet();		
	private IndexCategory parent = null;
	private String name = "";

	/**
	 * Create a new IndexCategory with a parent and a name
	 * @param theParent the parent of this category, can be null if this is the root
	 * @param theName a string representation of the IndexCategory
	 */
	protected IndexCategory(IndexCategory theParent, String theName)
	{		
		parent = theParent;
		name = theName;
	}

	/**
	 * Add a category to the children of this category. This is protected, there is no official way to add a 
	 * category from outside. This is only done by adding images that make up for a new category.
	 * All child-categories must be unique by their name. If a category with the given name does already exist,
	 * it is returned and no new one is added.
	 * @param tag the image-tag that is used to represent the name value of this category 
	 * @param name the name value for this category
	 * @return the added IndexCategory or the existing one with that name respectively 
	 */
	protected IndexCategory addCategory(String tag, String name)
	{
		IndexCategory cat = getCategory(name);
		if (cat == null)
		{
			cat = new VisibleIndexCategory(tag, name, this);
			categories.add(cat);
		}
		return cat;
	}

	/**
	 * Add an image to the category. This is also for internal purposes as it just updates internal collections.
	 * To add an image from outside to the whole Category-Tree all categorizeImage on the root.
	 * @param image an object implementing the ImageDataContainer interface
	 * @param resort whether the images should automatically be resorted
	 * @return the to-be-added image
	 */
	protected abstract ImageDataContainer addImage(ImageDataContainer image, boolean resort);


	/**
	 * Clears this IndexCategory and recursively all of its children. This includes removing all images and 
	 * categories contained.
	 */
	public void clear ()
	{
		Iterator catIt = getCategories().iterator();
		while (catIt.hasNext())
		{
			IndexCategory vic = (IndexCategory) catIt.next();
			vic.clear();
		}		
		categories.clear ();
	}

	/**
	 * Retrieve all images contained in this Category-Tree
	 * @return a list of ImageDataContainer 
	 */
	public abstract List getAllImages();

	/**
	 * Retrieve all categories directly contained in this category
	 * @return a list of IndexCategory
	 */
	public List getCategories()
	{
		return new ArrayList (categories);
	}

	/**
	 * Retrieve the direct child-category with the given name
	 * @param catName the name of the to-be-retrieved category
	 * @return the IndexCategory if found, null otherwise 
	 */
	protected IndexCategory getCategory(String catName)
	{
		Iterator ci = categories.iterator();
		while (ci.hasNext())
		{
			IndexCategory cat = (IndexCategory) ci.next();
			if (cat.getName().equals(catName))
			{
				return cat;
			}
		}
		return null;
	}

	/**
	 * Return the name of this IndexCategory
	 * @param catName the name of the to-be-retrieved category
	 * @return the IndexCategory if found, null otherwise 
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Sets the name of this IndexCategory. This is different from rename as it just
	 * updates the internal property name and has no effect on the images, etc. contained
	 * in the category or its subcategories. Call rename from outside to change a category's
	 * name together with the associated information on the images.
	 * @param newName the new Name of the category.
	 */
	protected void setName(String newName)
	{
		name = newName;
	}

	/**
	 * Return the parent of the category. Null if this is the root
	 * @return the parent of the category 
	 */
	public IndexCategory getParent()
	{
		return parent;
	}

	/**
	 * Return the root of this category tree
	 * @return the RootIndexCategory of this category tree 
	 */
	public abstract RootIndexCategory getRoot();

	/**
	 * Return if this category contains subcategories
	 * @return true if subcategories exist 
	 */
	public boolean hasCategories()
	{
		return !categories.isEmpty();
	}

	/**
	 * Refresh the subcategories. Removes empty categories. This is done after an image is added
	 * with recategorize, removed with removeImage or removeAll or refreshed with refreshImage 
	 */
	protected void refresh ()
	{
		// remove emtpy categories
		Iterator catIt = categories.iterator();
		while (catIt.hasNext())
		{
			VisibleIndexCategory vic = (VisibleIndexCategory) catIt.next();
			vic.refresh ();
			if (!vic.hasImages() && !vic.hasCategories())
			{
				catIt.remove();				
			}
		}		
	}
	
	/**
	 * Rename the index category including an update of the images
	 * included in this category or one of its subcategories. Call canBeRenamed first to find
	 * out if this is possible.
	 * this is currently only safe for single tags, not arrays
	 * @param newName the new name of the category and therefore the new value of the associated tag on all images contained in this category or its subcategories
	 */
	public abstract void rename (String newName);
	
	/**
	 * Tells if this category can be renamed. That means that there is some updateable information on 
	 * the contained images behind that makes sense to reflect the update on the Image Information
	 * @param true if category can be renamed
	 */
	public abstract boolean canBeRenamed ();
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString()
	{
		return getName();
	}

	/**
	 * Resorts all the images using the default comparator recursively through all categories 
	 */
	public void resortAll ()
	{
		Iterator catIt = categories.iterator();
		while (catIt.hasNext())
		{
			VisibleIndexCategory vic = (VisibleIndexCategory) catIt.next();
			vic.resortAll ();
		}		
	}
	
	public void exportToFolder (File folder)
	{
		if (folder.exists() && folder.isDirectory())
		{
			Iterator catIt = categories.iterator();
			while (catIt.hasNext())
			{
				IndexCategory vic = (IndexCategory) catIt.next();
				File newFolder = new File (folder,validFolderName(vic.getName()));
				newFolder.mkdir();
				vic.exportToFolder(newFolder);
			}		
		}		
	}

	public static String validFolderName (String name)
	{
		StringBuffer newName = new StringBuffer (name.length());
		
		for (int i=0;i<name.length();i++)
		{
			switch (name.charAt(i))
			{
				case '\\':
				case '/':
				case ':':
				case '*':
				case '?':
				case '"':
				case '>':
				case '<':
				case '|':
					break;
				default:
					newName.append (name.charAt(i));					
					break;
			}
		}
		return newName.toString();
	}
}
